<?php
namespace Pinenut\Service;
use Pinenut\Response\LiteException;
use Pinenut\Response\Exception;
/**
 * 
 * Диспечер действий. Базовый вариант "в лоб".
 * 
 * @author kosh
 *
 */
class Dispatcher {
	
	use \Pinenut\Di\Injectable;
	
	static protected $contexts = array();
	
	protected static $postprocessingMethod = '';
	
	
	/**
	 * Текущий контекст выполнения
	 * @var \Pinenut\Context
	 */
	static public $currentContext = null;

	static function dispatch(\Pinenut\Context $context) {
		\Log::begin(null, 'Application');
		
		// TODO: добавить контекст в пул, сделать его активным
		static::$currentContext = $context;

		static::beforeMainRequest();

		\Log::end('Dispatch: Before Main Request');
		\Log::begin(null, 'Application');
		
		// исполняем основное действие
		$response = static::action($context->getMainRequest());
		
		\Log::end('Dispatch: Main Request');
		\Log::begin(null, 'Application');
		
		// если возвращаем исключение - возбуждем его
		if (null !== $response->exception 
			&& $response->exception instanceof LiteException) {
			$e = new Exception();
			$e->setLiteException($response->exception);
			
			throw $e;
		}
		\Log::end('Dispatch: Error');
		\Log::begin(null, 'Application');
		
		// исполняем отложенные действия
		$response = static::doDeferred($response);
		\Log::end('Dispatch: Deferred');
		\Log::begin(null, 'Application');
		
		// для конечной генерации выполняем какой-нить постпроцессинг
		if (is_callable ( static::$postprocessingMethod )) {
			$response = call_user_func ( static::$postprocessingMethod, $response );
		}
		// устанавливаем серверное время
		// TODO: сделать общую схему подключения нескольких простроцесс хэндлеров
		$response->content = str_replace('<!-- __SERVER_TIME__ --!>', time(), $response->content);
		
		
		\Log::end('Dispatch: Postprocessing');
		
		return $response;
	}

	public static function beforeMainRequest()
	{

	}

	/**
	 * Перенаправляет пользователя на адрес, вычисляемый по указанному контексту
	 *
	 * @param Dispatcher $context - контекст
	 */
	protected static function contextRedirect(\Context $context) {

		$action = $context->mainRequest->action;
		$params = $context->mainRequest->params();

		$urlManager = new \UrlManager();
		$url = $urlManager->createUrl($action, $params);

		header('Location:' . $url);
	}

	/**
	 * Делаем отложенные действия
	 * @param \Pinenut\Response $response
	 * @return \Pinenut\Response
	 */
	static function doDeferred(\Pinenut\Response $response) {
		
		// первоначально населяем очередь запросов из отложенных запросов ответа
		$requests = $response->get('DeferredRequest');
		$allPlaceholders = [];
		// крутимся пока в очереди есть запросы
		while ($requests) {
			$nextRequests = [];
			$placeholders = [];
			$contents = [];
			$commands = [];
			// для каждого запроса
			foreach ( $requests as $placeholder =>  $deferredRequest) {
				// отмечаем что действие у нас уже встречалось
				$allPlaceholders [$placeholder] = true;
		
				// исполняем запрос
				$deferredResponse = static::action ( $deferredRequest );
				
				// получаем из ответа еще пачку отложенных 
				// действий и добавляем их в очередь
				$req = $deferredResponse->get('DeferredRequest');
				foreach ( (array)$req as $pl=>$req) {
					// добавляем в список на исполнение если раньше не был исполнен
					if (!isset($allPlaceholders[$pl])) {
						$nextRequests[$pl] = $req;
					}
				}
		
				// набиваем соответствующие массивы данным на подмену и исполнение команд
				$placeholders [] = $placeholder;
				$contents [] = ( string ) $deferredResponse;
				$commands = array_merge($commands, $deferredResponse->getCommands());
			}
			// подменяем плейсхолдер на содержимое
			$response->content = str_replace ( $placeholders, $contents, $response->content );
			// отыгрываем каналы
			$response->playCommands($commands);
			
			// играем следующую пачку если есть
			$requests = $nextRequests;
				
		}
		return $response;
	}
	
	/**
	 * @param \Pinenut\Request $request
	 * @return \Pinenut\Response
	 */
	static function action($request) {

		$context = static::$currentContext;
		$context->setGlobalParams($request);

		// если запрос без неявных параметров или с явными, но на корневом
		// уровне - выполняем
		// иначе возвращаем плейсхолдер и откладываем запрос на потом в канал
		if ($request->hasPullParams()
			&& !$request->isRoot()
			&& !$request->isDeferred()) {
				
			\Log::begin($request->action . '[deferred]', 'Actions');
			
			$context->pushRequest($request);
			// добавляем в стек действие				
			$request->isDeferred(true);
						
			// если нет ответа - инициируем его
			$placeholder = '<!-- Deferred request: '.$request->getKey() . '-->';
			$response = new \Response ();
			$response->offset('DeferredRequest', $request, $placeholder);
			$response->content = $placeholder;
			
			$context->addResponse($response);
			$context->popRequest();
			
			\Log::end();
			return $response;
		}
		
		
		\Log::begin($request->action, 'Actions');
		
	
		// добавляем в стек действие
		$context->pushRequest($request);
	
		// проверяем - может уже есть готовый ответ
		$response = $context->getResponse();

//		if (!  $response instanceof \Pinenut\Response) {
			// надо бы действие исполнить
			
			$response = static::doAction($request, $response);
						
			// добавляем ответ для текущего действия
			$context->addResponse($response);
//		}
		// убираем из стека это действие
		$context->popRequest();
	
		\Log::end(null, count($response->get('tags')));
		
		return $response;
	}
	
	/**
	 * @param \Pinenut\Request $request
	 * @param \Pinenut\Response $response
	 * @param number $skipStages
	 * @return Ambigous <\Pinenut\Response, \Response>
	 */
	static protected function doAction($request, $response, $skipStages = 0) {

		$context = static::$currentContext;
		
		
		$stages = $request->getStages();

		$action = $request->getAction();

		// нужно получить привилегии и проверить - есть ли у нас доступ

		$privileges = \Auth::normalizePrivileges( $action->privileges() );

		if ($context->checkAccess($privileges)) {
			// доступ есть, делаем действие

			foreach ($stages as $stage) {
				if ($skipStages-- <= 0 ){
					// выполняем действие, точнее его этап
					$response = $action->doStage($stage, $response);
	
					// накатываем изменения данных каналов из действий,
					// которые были выполнены внутри только что отыгранного
	
					//if ($stagesCount > 2)
					$response->playChannels();
				}
			}

			//if ($stagesCount <= 2) $response = static::playChannels($response);

			// устанавливаем привилегии в ответ

		} else {
			// доступа нет, модифицируем ответ соответствующим образом
			$response = new \Response;
			$response->setException(new \Exception('Access denied', 403));
		}
			
		return $response;

	}
}